// ========================================================================
// Copyright (c) 2003 Mort Bay Consulting (Australia) Pty. Ltd.
// $Id: Main.java,v 1.17.2.3 2003/10/12 03:08:59 gregwilkins Exp $
// ========================================================================

package org.mortbay.start;

import java.io.BufferedReader;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.FileInputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.Policy;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.StringTokenizer;




/*-------------------------------------------*/
/** Main start class.
 * This class is intended to be the main class listed in the MANIFEST.MF  of
 * the start.jar archive. It allows an application to be started with the
 * command "java -jar start.jar".
 *
 * The behaviour of Main is controlled by the "org/mortbay/start/start.config"
 * file obtained as a resource or file. This can be overridden with the START
 * system property.
 *
 * The format of each line in this file is:<PRE>
 *  SUBJECT [ [!] CONDITION [AND|OR] ]*
 * </PRE>
 * where SUBJECT:<PRE> 
 *   ends with ".class" is the Main class to run.
 *   ends with ".xml" is a configuration file for the command line
 *   ends with "/" is a directory from which add all jar and zip files from. 
 *   ends with "/*" is a directory from which add all unconsidered jar and zip files from.
 *   Containing = are used to assign system properties.
 *   all other subjects are treated as files to be added to the classpath.
 * </PRE>
 * Subjects may include system properties with $(propertyname) syntax. 
 * File subjects starting with "/" are considered absolute, all others are relative to
 * the home directory.
 * <P>
 * CONDITION is one of:<PRE>
 *   always
 *   never
 *   available package.class
 *   java OPERATOR n.n
 *   nargs OPERATOR n
 *   OPERATOR := one of "<",">","<=",">=","==","!="
 * </PRE>
 * CONTITIONS can be combined with AND OR or !, with AND being the assume
 * operator for a list of CONDITIONS.
 * Classpath operations are evaluated on the fly, so once a class or jar is
 * added to the classpath, subsequent available conditions will see that class.
 *
 * The system parameter CLASSPATH, if set is given to the start classloader before
 * any paths from the configuration file.
 *
 * Programs started with start.jar may be stopped with the stop.jar, which connects
 * via a local port to stop the server. The default port can be set with the 
 * STOP.PORT system property (a port of < 0 disables the stop mechanism). If the STOP.KEY 
 * system property is set, then a random key is generated and written to stdout. This key 
 * must be passed to the stop.jar.
 *
 * @author Jan Hlavaty (hlavac@code.cz)
 * @author Greg Wilkins
 * @version $Revision: 1.17.2.3 $
 */
 
public class Main
{
    static boolean _debug = System.getProperty("DEBUG",null)!=null;
    
    private String _classname = null;
    private Classpath _classpath = new Classpath();
    private String _config = System.getProperty("START","org/mortbay/start/start.config");
    private ArrayList _xml = new ArrayList();
       
    public static void main(String[] args)
    {
        try
        {
            new Main().start(args);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
    
    
    static File getDirectory(String name)
    {
        try
        {
            if (name != null)
            {
                File dir = new File(name).getCanonicalFile();
                if (dir.isDirectory())
                {
                    return dir;
                }
            }
        } catch (IOException e) { }
        return null;
    }
    

    boolean isAvailable(String classname)
    {
        try
        {
            Class.forName(classname);
            return true;
        }
        catch (ClassNotFoundException e)
        {}
        
        ClassLoader loader=_classpath.getClassLoader();
        try
        {
            loader.loadClass(classname);
            return true;
        }
        catch (ClassNotFoundException e)
        {}

        return false;
    }

    public static void invokeMain(ClassLoader classloader, String classname, String[] args)
         throws IllegalAccessException,
                InvocationTargetException,
                NoSuchMethodException,
                ClassNotFoundException
    {
        Class invoked_class = null;
        invoked_class = classloader.loadClass(classname);
        Class[] method_param_types = new Class[1];
        method_param_types[0] = args.getClass();
        Method main = null;
        main = invoked_class.getDeclaredMethod("main",method_param_types);
        Object[] method_params = new Object[1];
        method_params[0] = args;
        main.invoke(null,method_params);
    }
    
    /* ------------------------------------------------------------ */
    String expand(String s)
    {
        int i1=0;
        int i2=0;

        while (s!=null)
        {
            i1=s.indexOf("$(",i2);
            if (i1<0)
                break;
            i2=s.indexOf(")",i1+2);
            if (i2<0)
                break;

            String property=System.getProperty(s.substring(i1+2,i2),"");
            s=s.substring(0,i1)+property+s.substring(i2+1);
        }
        return s;
    }
    
    
    /* ------------------------------------------------------------ */
    void configure(InputStream config,String[] args)
        throws Exception
    {
        BufferedReader cfg = new BufferedReader(new InputStreamReader(config,"ISO-8859-1"));
        Version java_version = new Version(System.getProperty("java.version"));
        Version ver = new Version();
        
        // JAR's already processed
        java.util.Hashtable done = new Hashtable();
        
	// Initial classpath
	String classpath = System.getProperty("CLASSPATH");
	if (classpath!=null)
	{
	    StringTokenizer tok = new StringTokenizer(classpath,File.pathSeparator);
            while (tok.hasMoreTokens())
                _classpath.addComponent(tok.nextToken());
	}
        
        // Handle line by line
        String line = null;
        while (true)
        {
            line=cfg.readLine();
            if (line==null)
                break;
            if (line.length()==0 || line.startsWith("#"))
                continue;

            try
            {
                StringTokenizer st = new StringTokenizer(line);
                String subject = st.nextToken();
                boolean expression = true;
                boolean not = false;
                String condition=null;

                // Evaluate all conditions
                while (st.hasMoreTokens())
                {
                    condition = st.nextToken();
                    if (condition.equalsIgnoreCase("!"))
                    {
                        not=true;
                        continue;
                    }
                    if (condition.equalsIgnoreCase("OR"))
                    {
                        if (expression)
                            break;
                        expression=true;
                        continue;
                    }

                    if (condition.equalsIgnoreCase("AND"))
                    {
                        if (!expression)
                            break;
                        continue;
                    }

                    boolean eval=true;
                    
                    if (condition.equals("true") || condition.equals("always"))
                    {
                        eval=true;
                    }
                    else if (condition.equals("false") ||condition.equals("never"))
                    {
                        eval=false;
                    }
                    else if (condition.equals("available"))
                    {
                        String class_to_check = st.nextToken();
                        eval= isAvailable(class_to_check);
                    }
                    else if (condition.equals("exists"))
                    {
                        try
                        {
                            eval=false;
                            File file = new File(expand(st.nextToken()));
                            eval=file.exists();
                        }
                        catch(Exception e)
                        {
                            if (_debug) e.printStackTrace();
                        }
                    }
                    else if (condition.equals("java"))
                    {
                        String operator = st.nextToken();
                        String version = st.nextToken();
                        ver.parse(version);
                        eval =
                            (operator.equals("<") && java_version.compare(ver)<0) ||
                            (operator.equals(">") && java_version.compare(ver)>0) ||
                            (operator.equals("<=") && java_version.compare(ver)<=0) ||
                            (operator.equals("=<") && java_version.compare(ver)<=0) ||
                            (operator.equals("=>") && java_version.compare(ver)>=0) ||
                            (operator.equals(">=") && java_version.compare(ver)>=0) ||
                            (operator.equals("==") && java_version.compare(ver)==0) ||
                            (operator.equals("!=") && java_version.compare(ver)!=0);
                    }
                    else if (condition.equals("nargs"))
                    {
                        String operator = st.nextToken();
                        int number = Integer.parseInt(st.nextToken());
                        eval=
                            (operator.equals("<") && args.length<number) ||
                            (operator.equals(">") && args.length>number) ||
                            (operator.equals("<=") && args.length<=number) ||
                            (operator.equals("=<") && args.length<=number) ||
                            (operator.equals("=>") && args.length>=number) ||
                            (operator.equals(">=") && args.length>=number) ||
                            (operator.equals("==") && args.length==number) ||
                            (operator.equals("!=") && args.length!=number);
                    }
                    else
                    {
                        System.err.println("ERROR: Unknown condition: "+condition);
                        eval=false;
                    }

                    expression &= not?!eval:eval;
                    not=false;
                }
                        
                String file=expand(subject).replace('/',File.separatorChar);
                
                if (_debug)
                    System.err.println((expression?"T ":"F ")+line);
                
                if (!expression)
                {
                    done.put(file,file);
                    continue;
                }

                // Handle the subject
                if (subject.indexOf("=")>0)
                {
                    int i = file.indexOf("=");
                    String property=file.substring(0,i);
                    String value=file.substring(i+1);
                    if (_debug) System.err.println("  "+property+"="+value);
                    System.setProperty(property,value);
                }
                else if (subject.endsWith("/*"))
                {
                    // directory of JAR files
                    File extdir = new File(file.substring(0,file.length()-1));
                    File[] jars = extdir.listFiles(new FilenameFilter()
                        {
                            public boolean accept(File dir, String name)
                            {
                                String namelc = name.toLowerCase();
                                return namelc.endsWith(".jar") || name.endsWith(".zip");
                                
                            }
                        } );
                    
                    
                    for (int i=0; jars!=null && i<jars.length; i++)
                    {
                        String jar = jars[i].getCanonicalPath();
                        if (!done.containsKey(jar))
                        {
                            done.put(jar,jar);
                            boolean added=_classpath.addComponent(jar);
                            if (_debug)
                                System.err.println((added?"  CLASSPATH+=":"  !")+jar);
                        }
                    }
                }
                else if (subject.endsWith("/"))
                {
                    // class directory
                    File cd = new File(file);
                    String d = cd.getCanonicalPath();
                    if (!done.containsKey(d))
                    {
                        done.put(d,d);
                        boolean added=_classpath.addComponent(d);
                        if (_debug)
                            System.err.println((added?"  CLASSPATH+=":"  !")+d);
                    }
                }
                else if (subject.toLowerCase().endsWith(".xml"))
                {
                    // Config file
                    File f = new File(file);                        
                    if (f.exists())
                        _xml.add(f.getCanonicalPath());
                    if (_debug) System.err.println("  ARGS+="+f);
                }
                else if (subject.toLowerCase().endsWith(".class"))
                {
                    // Class
                    String cn = expand(subject.substring(0,subject.length()-6));
                    if (cn!=null && cn.length()>0)
                    {
                        if (_debug) System.err.println("  CLASS="+cn);
                        _classname=cn;
                    }
                }
                else
                {
                    // single JAR file
                    File f = new File(file);                        
                    String d = f.getCanonicalPath();
                    if (!done.containsKey(d))
                    {
                        done.put(d,d);
                        
                        boolean added=_classpath.addComponent(d);

                        if (!added)
                        {
                            added=_classpath.addClasspath(expand(subject));
                            if (_debug)
                                System.err.println((added?"  CLASSPATH+=":"  !")+d);
                        }
                        else if (_debug)
                            System.err.println((added?"  CLASSPATH+=":"  !")+d);
                    }
                }
            }
            catch (Exception e)
            {
                System.err.println("on line: '"+line+"'");
                e.printStackTrace();
            }
        }
    }
    
    
    /* ------------------------------------------------------------ */
    public void start(String[] args)
    {    
        // set up classpath:
        try
        {
            Monitor.monitor();
            InputStream cpcfg =getClass().getClassLoader().getResourceAsStream(_config);
            if (_debug) System.err.println("config="+_config);
            if (cpcfg==null)
                cpcfg=new FileInputStream(_config);
            
            configure(cpcfg,args);
            cpcfg.close();

	     File file = new File (System.getProperty("jetty.home"));
	     String canonical = file.getCanonicalPath();
	     System.setProperty("jetty.home", canonical);
	}
        catch (Exception e)
        {
            e.printStackTrace();
            System.exit(1);
        }
        
        // okay, classpath complete.
        System.setProperty("java.class.path",_classpath.toString());
        ClassLoader cl = _classpath.getClassLoader();

        if (_debug) System.err.println("java.class.path="+System.getProperty("java.class.path"));
        if (_debug) System.err.println("jetty.home="+System.getProperty("jetty.home"));
        if (_debug) System.err.println("java.io.tmpdir="+System.getProperty("java.io.tmpdir"));
        if (_debug) System.err.println("java.class.path="+_classpath);
        if (_debug) System.err.println("classloader="+cl);
        if (_debug) System.err.println("classloader.parent="+cl.getParent());

        // Invoke main(args) using new classloader.
        Thread.currentThread().setContextClassLoader(cl);
            
	// re-eval the policy now that env is set
	try
	{
	     Policy policy = Policy.getPolicy();
	     if (policy != null)
		 policy.refresh();
	}
	catch (Exception e)
	{
	    e.printStackTrace();
	}

        try
        {
            if (_xml.size()>0)
            {
                for (int i=0;i<args.length;i++)
                    _xml.add(args[i]);
                args=(String[])_xml.toArray(args);
            }
                

	    //check for override of start class
	    String serverOverride = System.getProperty("jetty.server");
	    if (_debug) System.err.println ("server.override="+serverOverride);

	    if (serverOverride != null)
		_classname = serverOverride;
            invokeMain(cl,_classname,args);            
        }
        catch (Exception e)
        {
            e.printStackTrace();
        } 
    }


}
